using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Xml.Serialization;
using System.IO;
using System.Xml;
using System.Reflection;

namespace Snooze.Model
{
    using Castle.ActiveRecord;
    using Castle.ActiveRecord.Framework.Config;
    using System.Diagnostics;
    using System.ComponentModel;

	/// <summary>
	/// 
	/// </summary>
    public sealed class Serializer
    {
        public const int MAX_DEPTH = 25;
        private static readonly Serializer instance = new Serializer();

        // the path followed
        private List<Type> type_path;
        private int depth;
        private Serializer() { }

        static readonly object padlock = new object();
        private object objectToUpdate;

        public static Serializer Instance
        {
            get
            {
                return instance;
            }
        }

        public string Serialize(Persistent obj, Persistent parent, int depth)
        {
            if (parent == null)
            {
                this.depth = depth;
                this.type_path = new List<Type>();
            }
            return GetXml(obj);
        }

        public void Deserialize(Persistent obj, string xml, int depth)
        {
            this.depth = depth;
            this.type_path = new List<Type>();
            SetXml(obj, xml);
        }

        private string GetXml(Persistent obj)
        {
            // check for depth
            if (type_path.Count > depth)
            {
                return "";
            }

            // check if type already exists as parent
            for (int i=type_path.Count-2; i>=0; i--)
            {
                if (type_path[i] == obj.GetType())
                {
                    return "";
                }
            }

            type_path.Add(obj.GetType());
            XmlSerializer var = new XmlSerializer(obj.GetType());
            StringWriter s = new StringWriter();
            XmlTextWriter writer = new XmlTextWriter(s);
            writer.Formatting = Formatting.Indented;
            var.Serialize(writer, obj);
            writer.Close();
			
			Console.WriteLine("XML serialize:" + s.ToString());

            XmlDocument doc = new XmlDocument();
            doc.LoadXml(s.ToString());

            return doc.DocumentElement.OuterXml;
        }

        private void SetXml(Persistent obj, string xml)
        {

            

            // check for depth
            if (type_path.Count > depth)
            {
                return;
            }

            // check if type already exists as parent
            for (int i = type_path.Count - 2; i >= 0; i--)
            {
                if (type_path[i] == obj.GetType())
                {
                    return;
                }
            }

            type_path.Add(obj.GetType());
            XmlSerializer var = new XmlSerializer(obj.GetType());
            StringReader sr = new StringReader(xml);
            XmlReader reader = new XmlTextReader(sr);

            lock (padlock)
            {
                this.objectToUpdate = obj;
                var.Deserialize(reader);
            }
        }

        public void WriteXml(XmlWriter writer, Persistent obj)
        {
            PropertyInfo[] props = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

            foreach (PropertyInfo prop in props)
            {
                string name = prop.Name;
                if (name != "Xml")
                {
                    object value = prop.GetValue(obj, null);
                    if (name != null && value != null)
                    {
                        // check if property is a IList<Persistent>
                        Type genericType = typeof(IList<>);
                        Type PersistentCollectionType = null;
                        if (prop.PropertyType.IsGenericType)
                        {
                            Type[] gen_args = prop.PropertyType.GetGenericArguments();
                            if (gen_args.Length == 1 && (typeof(Persistent).IsAssignableFrom(gen_args[0])))
                            {
                                PersistentCollectionType = genericType.MakeGenericType(gen_args);
                            }
                        }

                        // loop over collections
                        if (PersistentCollectionType != null && PersistentCollectionType.IsAssignableFrom(prop.PropertyType))
                        {
                            IList list = (IList)value;
                            foreach (Persistent o in list)
                            {
                                writer.WriteRaw(Serializer.Instance.Serialize(o, obj, depth));
                            }
                        }
                        else
                        {
                            if (typeof(Persistent).IsAssignableFrom(prop.PropertyType))
                            {
                                writer.WriteRaw(Serializer.Instance.Serialize((Persistent)value, obj, depth));
                            }
                            else
                            {
                                writer.WriteElementString(name, "", value.ToString());
                            }
                        }
                    }
                }
            }
        }

        public void ReadXml(XmlReader reader)
        {
            string e_name;
            string e_value;


            Console.WriteLine("parsing object: " + reader.Name);
            reader.Read();

            while (reader.Read())
            {
                if (reader.Depth > depth) continue;
                
                // must be an element 
                if (reader.IsStartElement())
                {
                    // get the property name
                    e_name = reader.Name;

                    // skip Xml en Id property
                    if (e_name == "Xml" || e_name == "Id") continue;

                    // read the next node
                    if (!reader.IsEmptyElement) reader.Read();

                    // parsing an objects properties from childnodes
                    if (!reader.IsEmptyElement && reader.IsStartElement())
                    {
                        // this is an object or collection
                        Console.Write("TODO:");
                        Debugger.Break();
                    }
                    else
                    {
                        e_value = reader.Value;

                        // this is a single property
                        PropertyInfo p = objectToUpdate.GetType().GetProperty(e_name, BindingFlags.Public | BindingFlags.Instance);

                        TypeConverter tc = TypeDescriptor.GetConverter(p.PropertyType);
                        if (tc.CanConvertFrom(typeof(string)))
                        {
                            try
                            {
                                object val = tc.ConvertFrom(e_value);
                                p.SetValue(objectToUpdate, val, null);
                            }
                            catch (Exception e)
                            {
                                Trace.WriteLine("Setting " + e_name + " failed:\r\n" + e.Message);
                            }
                        }
                        
                    }
                }
            } 

        }
    }

}
